﻿package modelo;

import interfacesRemotas.*;

import java.io.Serializable;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.UnknownHostException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.ListIterator;

import modelo.gestionCarta.Carta;
import modelo.gestionPedidos.Pedido;
import modelo.gestionReserva.*;



/**
 * Esta clase representa al Restaurante de la aplicación.
 * Además de los datos propios del restaurante, contiene una lista de mesas, 
 * una lista de reservas y una lista de pedidos, así como una carta asociada.
 * @version 2.0
 * @created 02-may-2007 20:01:00
 * @author  Enrique Menor Santos,Luis Miguel Merino Bonilla, Eusebio J. Aguilera Aguilera
 */
public class Restaurante implements Serializable 
{
	
	/**
	 * 
	 */
	private static final long serialVersionUID = -7097257949657053780L;
	private int id_restaurante;
	private String nombre;
	private String CIF;
	private String direccion;
	private int tlfno;
	/**
	 * Carta asociada al restaurante
	 */
	private Carta m_Carta;
	/**
	 * Array que contiene las mesas del restaurante
	 */
	private List<Mesa> m_Mesa;
	/**
	 * lista de reservas del restaurante
	 */
	private ArrayList<Reserva> m_Reserva;
	/**
	 * Lista de pedidos existentes en un momento determinado en el restaurante
	 */
	private List<Pedido> listaPedidos;
	
	/**
	 * Cadena que contiene la direccion ip donde se esta ejecutando el servidor RMI.
	 * Esto es necesario para la ejecucion de objetos remotos.
	 */
	private String ipServidorRMI = null;
	
	/**
	 * Constructor con dos parametros.
	 * Este es el constructor que se debe usar para hacer uso de la tacnologia RMI.
	 * En caso de no encontrar el restaurante, se creará un objeto con id = -1, para que el programador sepa 
	 * que no ha sido posible la instanciación.
	 * @param id Identificador del restaurante.
	 * @param ip Direccion ip del servidor RMI.
	 */
	public Restaurante(int id, String ip) 
	{
		//accedemos a la capa física para obtener el objeto restaurante
		
		Object[] arrayDatos;
		try {
			// Accedemos al objeto remoto AccesoRestaurante mediante la interfaz InterfazRestaurante
			InterfazRestaurante ar = (InterfazRestaurante)Naming.lookup("//" + ip + "/AccesoRestaurante");
			
			//Obtenemos el objeto restaurante de la base de datos
			arrayDatos = ar.recuperarRestaurante(id);
			
			//chequeo de errores
	    	if(arrayDatos == null)
	    		this.id_restaurante=-1;
	    	else
	    	{
	    		//la obtención de la BD ha sido existosa: copiamos el objeto obtenido
	    		this.id_restaurante=id;
	    		this.nombre=(String)arrayDatos[0];
	    		this.CIF=(String)arrayDatos[1];
	    		this.direccion=(String)arrayDatos[2];
	    		this.tlfno=(int)((Integer)arrayDatos[3]);
	    		//System.err.println("Datos del restaurante recuperados de la BD: "+nombre+" "+CIF+" "+direccion+" "+tlfno);
	    		this.ipServidorRMI=ip;
	    		this.m_Carta = getCarta();				//TODO acceso a BD
	    		this.m_Mesa = recuperarMesas();			//acceso a BD
	    		this.m_Reserva = recuperarReservas();	//acceso a BD
	    		this.listaPedidos = recuperarPedidos();	//acceso a BD	
	    		
	    		
	    		if (m_Carta==null || m_Mesa==null || m_Reserva==null|| listaPedidos==null)
	    		{
	    			this.id_restaurante=-1;
	    			System.err.println("Error al crear el objeto restaurante:"
	    					+"m_Carta="+m_Carta+", m_Mesa= "+m_Mesa+" m_Reserva = "+m_Reserva+", listaPedidos="+listaPedidos);
	    		}
	    			
	    	}
		} catch (MalformedURLException e) {
			//e.printStackTrace();
			System.err.println("Error URL no valida en la clase Restaurante al crear el restaurante: " + e.getMessage());
		} catch (RemoteException e) {
			//e.printStackTrace();
			System.err.println("Error remoto en la clase Restaurante al crear el restaurante: " + e.getMessage());
		} catch (NotBoundException e) {
			//e.printStackTrace();
			System.err.println("Error Not Bound en la clase Restaurante al crear el restaurante: " + e.getMessage());
		}
	}
	/**
	 * Constuctor con un único parametro.
	 * Este constructor será llamado desde las aplicaciones para recuperar el restaurante desde la BD.
	 * Este constructor será ejecutado en el Servidor, por lo q el atributo ipServidorRMI será creado de forma automática
	 * usando la clase InetAdress.
	 * En caso de no encontrar el restaurante, se creará un objeto con id = -1, para que el programador sepa 
	 * que no ha sido posible la instanciación.
	 * @param código del restaurante que se va a buscar
	 * 
	 */
    public Restaurante (int id)
    {
    	//accedemos a la capa física para obtener el objeto restaurante
    	Restaurante r;
		try {
			// Accedemos al objeto remoto AccesoRestaurante mediante la interfaz InterfazRestaurante
			InterfazRestaurante ar = (InterfazRestaurante)Naming.lookup("//" + this.ipServidorRMI + "/AccesoRestaurante");
			
			r = ar.getRestaurante(id);
			
			//chequeo de errores
	    	if(r == null)
	    		this.id_restaurante=-1;
	    	else
	    	{
	    		//la obtención de la BD ha sido existosa: copiamos el objeto obtenido
	    		this.id_restaurante=r.getId();
	    		this.nombre=r.getNombre();
	    		this.CIF=r.getCIF();
	    		this.direccion=r.direccion;
	    		this.tlfno=r.getTlfno();
	    		this.m_Carta=r.recuperarCarta();	//TODO no se accede a BD
	    		this.m_Mesa=r.getMesas();			//no se accede a BD
	    		this.m_Reserva=r.getReservas();		//no se accede a BD
	    		this.listaPedidos = r.getPedidos();	//no se accede a BD
	    		this.ipServidorRMI = InetAddress.getLocalHost().getHostAddress();
	    			    		
	    		if (m_Carta==null || m_Mesa==null || m_Reserva==null|| listaPedidos==null || ipServidorRMI==null)
	    		{
	    			this.id_restaurante=-1;
	    			System.err.println("Error al crear el objeto restaurante:"
	    					+"m_Carta="+m_Carta+", m_Mesa= "+m_Mesa+" m_Reserva = "+m_Reserva+", listaPedidos="+listaPedidos+", ipServidorRMI="+ipServidorRMI);
	    		}
	    			
	    	}
		} catch (MalformedURLException e) {
			//e.printStackTrace();
			System.err.println("Error en la clase Restaurante al crear el restaurante: " + e.getMessage());
		} catch (RemoteException e) {
			//e.printStackTrace();
			System.err.println("Error en la clase Restaurante al crear el restaurante: " + e.getMessage());
		} catch (NotBoundException e) {
			//e.printStackTrace();
			System.err.println("Error en la clase Restaurante al crear el restaurante: " + e.getMessage());
		}catch (UnknownHostException e) {
			//e.printStackTrace();
			System.err.println("Error en la clase Restaurante al crear el restaurante: " + e.getMessage());
		}
    		
    }
	/**
	 * Constructor con parámetros del restaurante
	 * @param id
	 * @param n
	 * @param cif
	 * @param dir
	 * @param t
	 * @deprecated : este constructor está obsolet, debe usarse el contructor con dos parámetros
	 */
	public Restaurante(int id, String n, String cif, String dir, int t){
		this.id_restaurante=id;
		this.nombre=n;
		this.CIF=cif;
		this.direccion=dir;
		this.tlfno=t;
		this.m_Carta = getCarta();				//TODO acceso a BD
		this.m_Mesa = recuperarMesas();			//acceso a BD
		this.m_Reserva = recuperarReservas();	//acceso a BD
		this.listaPedidos = recuperarPedidos();	//acceso a BD		
	}
	
	
	
	/**
	 * Devuelve la ip del servidor RMI.
	 * @return la ip del servidor RMI.
	 */
	
	public String getIPServidorRMI()
	{
		return this.ipServidorRMI;
	}
	
	/**
	 * 
	 * @return el id
	 */
    public int getId()
    {
        return this.id_restaurante;
    }

	/**
	 * @return el cIF
	 */
	public String getCIF() {
		return CIF;
	}
	/**
	 * @return la direccion
	 */
	public String getDireccion() {
		return direccion;
	}
	/**
	 * @return el tlfno
	 */
	public int getTlfno() {
		return tlfno;
	}
	/**
	 * 
	 * @return el nombre
	 */
	public String getNombre(){
		return this.nombre;
	}
	/**
	 * TODO:
	 * Hay que acceder siempre a la BD por si ha habido cambios en la carta
	 * Este método sólo debería devolver la referencia al atributo Carta 
	 * Por compatibilidad con la 1º iteración,  accede a la BD y actualiza la carta del objeto restaurante.
	 * @return
	 */
	public Carta getCarta()
	{
		Carta m_Carta = null;
		try {
			InterfazCarta ac = (InterfazCarta)Naming.lookup("//" + this.ipServidorRMI + "/AccesoCarta");
			m_Carta = ac.getCarta(id_restaurante);
		} catch (MalformedURLException e) {
			System.err.println("Error en la clase Restaurante en el metodo getCarta: " + e.getMessage());
		} catch (RemoteException e) {
			System.err.println("Error en la clase Restaurante en el metodo getCarta: " + e.getMessage());
		} catch (NotBoundException e) {
			System.err.println("Error en la clase Restaurante en el metodo getCarta: " + e.getMessage());
		}
		
		return m_Carta;
	}
	/**
	 * TODO:
	 * Este método debería acceder a la BD y actualizar la carta del objeto restaurante.
	 * Por compatibilidad con la 1º iteración, sólo devolverá la referencia al atributo Carta
	 * @return
	 */
	public Carta recuperarCarta()
	{
		return this.m_Carta;
	}
		
	/**
	 *
	 * @param c
	 */
	public void agregarCarta(Carta c){
		m_Carta = c;
	}
	/**
	 * 
	 * @return la referencia a la lista de mesas del restaurante
	 */
	public List<Mesa> getMesas()
	{
		return this.m_Mesa;
	}
	/**
	 * Método que accede a la base de datos y actualiza la lista de mesas del restaurante
	 * @return lista de mesas actualizadas
	 */
	public List<Mesa> recuperarMesas()
	{
		
		try {
			
			InterfazRestaurante ar = (InterfazRestaurante)Naming.lookup("//" + this.ipServidorRMI + "/AccesoRestaurante");
			this.m_Mesa =  ar.getMesas(id_restaurante);
		} catch (MalformedURLException e) {
			//e.printStackTrace();
			System.err.println("Error en la clase Restaurante en el metodo actualizarMesas: " + e.getMessage());
		} catch (RemoteException e) {
			//e.printStackTrace();
			System.err.println("Error en la clase Restaurante en el metodo actualizarMesas: " + e.getMessage());
		} catch (NotBoundException e) {
			//e.printStackTrace();
			System.err.println("Error en la clase Restaurante en el metodo actualizarMesas: " + e.getMessage());
		}
		return this.m_Mesa;
	}
	
	/**
	 * Método que busca una mesa en la lista de mesas.
	 * Para esta búsqueda no se accede a la BD, 
	 * se recomienda ejecutar actualizarMesas previamente a este metodo
	 * @param numero
	 */
	public Mesa buscarMesa(int numero)
	{
		//control de errores
		if (numero < 0 || this.m_Mesa == null)
			return null;
		
		Mesa m=null;
		ListIterator<Mesa> it = this.m_Mesa.listIterator();
		boolean encontrado = false;
		
		while (!encontrado && it.hasNext())
		{
			m=it.next();
			encontrado = (m.getNumMesa() == numero);
		}
		if (encontrado)
			return m;
		else
			return null;
	}
	/**
	 * Método que consulta en la BD las mesas libres disponibles en el momento actual y
	 * devuelve una lista con todas ellas
	 * @param idRestaurante
	 * @return la lista de mesas libres y si no puede devuelve null
	 */
	public List<Mesa> obtenerMesasLibres(int idRestaurante)
	{
		List<Mesa> mesasLibres = null;
		
		try {
			InterfazRestaurante ar = (InterfazRestaurante)Naming.lookup("//" + this.ipServidorRMI + "/AccesoRestaurante");
			
			mesasLibres = ar.getMesasLibres(idRestaurante);
			
		} catch (MalformedURLException e) {
			//e.printStackTrace();
			System.err.println("Error en la clase Restaurante en el metodo obtenerMesasLibres: " + e.getMessage());
		} catch (RemoteException e) {
			//e.printStackTrace();
			System.err.println("Error en la clase Restaurante en el metodo obtenerMesasLibres: " + e.getMessage());
		} catch (NotBoundException e) {
			//e.printStackTrace();
			System.err.println("Error en la clase Restaurante en el metodo obtenerMesasLibres: " + e.getMessage());
		}
		
		return mesasLibres;
	}
	/**
	 * 
	 * @return la referencia a la lista de reservas
	 */
	public ArrayList<Reserva> getReservas(){
		return m_Reserva;
	}
	/**
	 * 
	 * @return la referencia a la lista de reservas actualizada desde la BD
	 */
	public ArrayList<Reserva> recuperarReservas()
	{
		try {
			InterfazReserva ar = (InterfazReserva)Naming.lookup("//" + this.ipServidorRMI + "/AccesoReserva");
			
			this.m_Reserva=ar.getReservas(id_restaurante);	//Accedemos a la BD
		} catch (MalformedURLException e) {
			System.err.println("Error (URL invalida) en la clase Restaurante en el metodo recuperarReservas: " + e.getMessage());
		} catch (RemoteException e) {
			System.err.println("Error (Excepcion Remota) en la clase Restaurante en el metodo recuperarReservas: " + e.getMessage());
		} catch (NotBoundException e) {
			System.err.println("Error (Not Bound) en la clase Restaurante en el metodo recuperarReservas: " + e.getMessage());
		}
		return this.m_Reserva;
	}

	public boolean agregarReserva(Reserva r){
		DateFormat df = DateFormat.getDateTimeInstance();
		Date d=null;;
		try {
			d=df.parse(r.getFecha()+" "+r.getHora());
		}catch(Exception e){
			System.err.println("Error en la clase Restaurante en el metodo agregrarReserva:" + e.getMessage());
		}
		
		boolean result = false;
		try {
			InterfazReserva ar = (InterfazReserva)Naming.lookup("//" + this.ipServidorRMI + "/AccesoReserva");
			result = ar.insertReserva(
					r.getNombreCliente(),
					d,
					d,
					r.getComensales());
		} catch (MalformedURLException e) {
			System.err.println("Error en la clase Restaurante en el metodo agregarReserva: " + e.getMessage());
		} catch (RemoteException e) {
			System.err.println("Error en la clase Restaurante en el metodo agregarReserva: " + e.getMessage());
		} catch (NotBoundException e) {
			System.err.println("Error en la clase Restaurante en el metodo agregarReserva: " + e.getMessage());
		}

		m_Reserva.add(r);

		return result;
	}

	public void cancelarReserva(Reserva r){
		r.anularReserva();
		this.getReservas().remove(r);
	}
	/**
	 * Método que accede a la base de datos y actualiza la lista de pedidos del restaurante
	 * @return lista de pedidos actualizados, en caso de fallo remoto se devuelve null
	 */
	public List<Pedido> recuperarPedidos()
	{
		InterfazPedido ap;
		
		try {
			ap =(InterfazPedido)Naming.lookup("//" + this.ipServidorRMI + "/AccesoPedido");
			this.listaPedidos =ap.getPedidos(id_restaurante);
		} catch (RemoteException e) {
			System.err.println("Error Remoto al actualizar pedidos desde la clase Restaurante");
			return null;
		} catch (MalformedURLException e) {
			//e.printStackTrace();
			System.err.println("Error URL no válida al actualizar pedidos desde la clase Restaurante");
		} catch (NotBoundException e) {
			//e.printStackTrace();
			System.err.println("Error NotBound al actualizar pedidos desde la clase Restaurante");
		}
		
		return this.listaPedidos;		
	}
	/**
	 * Devuelve los pedidos actualmente en vigor del restaurante
	 * @return la referencia a la lista de pedidos
	 */
	public List<Pedido> getPedidos()
	{
		return this.listaPedidos;	
	}
	/**
	 * Devuelve un pedido concreto mediante su codigo.
	 * La búsqueda se realiza en memoria, por tantose recomienda llamar al
	 * método actualizarPedidos() antes.
	 * @param codigo particular de un pedido
	 * @return el Pedido solicitado en caso de existir, null en caso contrario
	 */
	public Pedido getPedido(int codigo)
	{
		if (codigo < 0 || this.listaPedidos == null)
			return null;
		//procedemos con la búsqueda
		Pedido p=null;
		
		boolean encontrado = false;
		ListIterator<Pedido> it =  this.listaPedidos.listIterator();
		while(!encontrado && it.hasNext())
		{
			p = (Pedido)it.next();
			encontrado = (p.getCodPedido()== codigo);
		}
		if (!encontrado)
			return null;
		return p;
	}
	

	/**
	 * Método que añade un pedido nuevo a la lista de pedidos del restaurante
	 * @param mesas
	 * @param numComensales
	 * @return
	 */
	public boolean anadirPedido (List<Mesa> mesas, int numComensales)
	{
		//Se crea el pedido en memoria
		Pedido p = new Pedido(numComensales, mesas,this.ipServidorRMI);
		//Intentamos crearlo en la BD
		if(p.insertarPedido(this.id_restaurante))
			this.listaPedidos.add(p);
		else
			return false;			
		//Si se ha podido agregar el pedido, modificamos el estado de las mesas
		mesasOcupadas(mesas);
		//si todo ha ido bien devolvemos true
		return true;		
	}
	/**
	 * Método que busca un pedido en la lista de pedidos y lo amplía con nuevas mesas y nuevos comensales
	 * @param p
	 * @param nuevasMesas
	 * @param nuevosComensales
	 * @return cierto si es posible ampliar o false en caso contrario
	 */
	public boolean ampliarPedido(Pedido p,List<Mesa> nuevasMesas, int nuevosComensales )
	{
		p.setNumComensales(nuevosComensales);
		p.agregarMesas(nuevasMesas);
		//Intentamos actualizar en la BD
		if(p.actualizarPedido())
			this.listaPedidos.add(p);
		else
			return false;
		//Si se ha podido agregar el pedido, modificamos el estado de las mesas
		mesasOcupadas(nuevasMesas);
		//si todo ha ido bien devolvemos true
		return true;
	}
	/**
	 * Método privado que comprueba si una lista de mesas está libre
	 * @param mesas
	 * @return cierto si todas lo están, falso en caso contrario
	 */
	/*
	
	private boolean mesasLibres(List<Mesa> mesas)
	{
		if(mesas.size()<=0)
			return false;
		boolean disponibles = true;
		ListIterator<Mesa> it =  mesas.listIterator();
		
		while(disponibles && it.hasNext())
		{
			Mesa m = it.next();
			disponibles = m.getEstado().equals("libre");
		}
		return disponibles;
	}*/
	/**
	 * Método privado que pone una lista de mesas 'param' como ocupadas
	 * @param mesas
	 */
	private void mesasOcupadas(List<Mesa> mesas)
	{
		ListIterator<Mesa> it =  mesas.listIterator();
	
		while(it.hasNext())
		{
			Mesa m = (Mesa)it.next();
			m.cambiarEstado("ocupado");	
		}
	}
	/**
	 * Elimina el pedido con el código que coincida con param
	 * @param codigo
	 * @return cierto si ha podido eliminarse, false en caso contrario
	 */
	public boolean eliminarPedido(int codigo)
	{
		Pedido p = getPedido(codigo);
		if (p == null)
			return false;
		//lo eliminamos de la base de datos
		if(!p.eliminarPedido())
			return false;
		//una vez eliminado de la base de datos lo eliminamos del sistema
		this.listaPedidos.remove(p);
		//llamamos explícita al recolector de basura
		System.gc();
		return true;		
	}
	
	
}
